package com.cxyd.generator.test;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @Author: huanglianyun
 * @Description:
 * @DATE: Created in 2020/6/4 15:27.
 */

/**
 * 详解 -》 https://www.jianshu.com/p/9f848ac72dfb
 *
 *      Stream 能实现的功能，for 循环都能实现，只是 Stream 代码一般比较简洁，可读性强。
 *      但在某些情况下，使用 for 循环要比 Stream 要简洁代码逻辑清晰
 */
public class StreamTest extends BaseTest{
    private List<User> userList = Arrays.asList(
            new User(101, "小明", 10, "男", "青海省", "西宁市"),
            new User(102, "小青", 12, "女", "宁夏回族自治区", "银川市"),
            new User(103, "小海", 8, "男", "西藏自治区", "拉萨市"),
            new User(108, "阿刁", 18, "女", "西藏自治区", "拉萨市"),
            new User(104, "小阳", 9, "女", "新疆维吾尔自治区", "乌鲁木齐市"),
            new User(105, "小强", 14, "男", "陕西省", "西安市"),
            new User(106, "小帅", 15, "男", "河北省", "石家庄市"),
            new User(107, "小云", 15, "女", "河北省", "石家庄市")
    );

    @Test
    public void testMap(){
        // select id from user
        //List<Integer> collect = userList.stream().map(e -> e.getId).collect(Collectors.toList());
        //userList.stream().map(User::getId).forEach(e -> System.out.println(e));
        List<Integer> collect = userList.stream().map(User::getId).collect(Collectors.toList());
        userList.stream().map(User::getId).forEach(System.out::println);
    }

    @Test
    public void testFilter(){
        // select * from user where age<10
        userList.stream()
                .filter(e-> e.getAge() < 10)
                .forEach(System.out::println);

    // select * from user where age<10 and gender='男'
        userList.stream()
                .filter(e->e.getAge() < 10 )
                .filter(e->e.getSex()=="男")
                .forEach(System.out::println);
    }

    //最值、总和、数量、均值(max, min, sum, count, average)
    @Test
    public void test3(){
        // select max(age), min(age), sum(age), count(age), avg(age) from user
        // max
                Optional<Integer> maxAge = userList.stream()
                        .map(e -> e.getAge())
                        .max(Comparator.comparingInt(x -> x));
        // 等同于
        // Optional<Integer> maxAge =  userList.stream()
        //  .map(e -> e.getAge())
        //  .max((x, y) -> x-y);

        // min
                Optional<Integer> minAge = userList.stream()
                        .map(e -> e.getAge())
                        .min(Comparator.comparingInt(x -> x));
        // sum
                Optional<Integer> sumAge = userList.stream()
                        .map(e -> e.getAge())
                        .reduce((e, u) -> e + u);
        // count
                long count = userList.stream()
                        .map(e -> e.getAge())
                        .count();
        // 平均值=总和/数量
    }

    //排序 order by - sorted
    @Test
    public void test4(){
        // select * from user order by age
        userList.stream()
                .sorted(Comparator.comparingInt(User::getAge))
                .forEach(System.out::println);

        //根据分数升序排序
        /**
         *需要注意的是，原来的数组是没有变化的，需要重新将排序后的数据集合进行toList()
         */
        //List<Student> sortedStudentASC = studentList.stream().sorted(Comparator.comparing(Student::getScore)).collet(Collectors.toList());

        //降序排序
        //List<Student> sortedStudentDESC = studentList.stream().sorted(Comparator.comparing(Student::getScore).reserved()).collet(Collectors.toList());
    }

    //分页 limit - skip、limit
    @Test
    public void test5(){
        // select * from user limit 5
        userList.stream()
                .limit(5)
                .forEach(System.out::println);

        // select * from user limit 5, 5
                userList.stream()
                        .skip(5)
                        .limit(5)
                        .forEach(System.out::println);

        // select * from user order by age limit 1
                userList.stream()
                        .sorted(Comparator.comparingInt(User::getAge))
                        .limit(1)
                        .forEach(System.out::println);
        // 或者
                Optional<User> minAgeUser = userList.stream()
                        .sorted(Comparator.comparingInt(User::getAge))
                        .findFirst();
    }

    /**
     * //是否存在 exists - anymatch
     *  返回值是一个，boolean类型！！！！
     */
    @Test
    public void test6() {
        // select exists(select * from user where name='小海')    }
        // 有没有名字叫“小海”的用户
        boolean exists0 = userList.stream()
                .anyMatch(e -> e.getName().equals("小海"));

        // select not exists(select * from user where name='小海')
        // 是不是没有名字叫“小海”的用户
        boolean exists1 = userList.stream()
                .noneMatch(e -> e.getName().equals("小海"));

        // 是不是所有用户年龄都小于10岁
        boolean exists2 = userList.stream()
                .allMatch(e -> e.getAge() < 10);
    }

    /**
     * 收集操作 collect
     * 收集操作就是遍历 stream 中的元素，并进行累加处理，即归约 reduction
     *
     * 前面提到的 max() min() count() reduce() 都属于 reduction operation
     *
     * 但 collect() 又和前面这几种归约操作有所区别，它是 Mutable reduction 动态归约
     *
     * Stream接口提供了2个collect方法：
     *      <R> R collect(Supplier<R> supplier,
     *                 BiConsumer<R, ? super T> accumulator,
     *                 BiConsumer<R, R> combiner);
     *
     *                 三个参数：
     *
     *                  供给者 supplier：负责提供动态容器，例如 Collectors、StringBuilder
     *                  累加器 accumulator：负责将流中的元素做累加处理
     *                  合并者 combiner ：负责将两个容器的元素合并在一起
     *
     *                  在串行流中，combiner 根本没有执行，所以随便写点啥满足参数对象就行。
     *                  如果说串行流是单线程，那么并行流就是多线程了
     *
     *      《=================================================================》
     *      <R, A> R collect(Collector<? super T, A, R> collector);
     */
    @Test
    public void test7(){
        /*ArrayList<String> strings = new ArrayList<>();
        for (T element : stream) {
            strings.add(element.toString());
        }
        // 等同于
        ArrayList<String> strings = stream.collect(() -> new ArrayList<>(),
                (c, e) -> c.add(e.toString()),
                (c1, c2) -> c1.addAll(c2));*/

        /**
         * 与其传递三个参数这么麻烦，还不如直接传递一个对象呢！
         * 这就是第二个 collect() 方法的由来，使用收集器 Collector 来替代三个参数
         *
         * 实际上，我们一般不需要自己创建 Collector 对象，Java8 提供了一个 Collectors 类，专门提供收集器 Collector 对象。毕竟我们平时能够使用到的收集操作也就那几种：转为集合对象、分组、统计。
         *
         */
        /*
        toCollection
            不使用收集器的情况下：collect中需要传递3个参数
            在 collect() 方法第二个参数累加器 accumulator (c, e) -> c.add(e) 这里，对流中元素进行了遍历，所以可以把流中元素添加到任意的集合容器中，List、Set、Map 等等

         */
        List<User> subUserList1 = userList.stream()
                .filter(e -> e.getAge() < 10)
                .filter(e -> e.getSex() == "男")
                .collect(() -> new ArrayList<>(),
                        (c, e) -> c.add(e),
                        (c1, c2) -> c1.addAll(c2));

        /*
        使用 Collectors 工具类提供的收集器
         */
        // toList()
        List<User> list = userList.stream()
                .filter(e -> e.getAge() < 10)
                .filter(e -> e.getSex() == "男")
                .collect(Collectors.toList());

        // toSet()
                Set<User> set = userList.stream()
                        .filter(e -> e.getAge() < 10)
                        .filter(e -> e.getSex() == "男")
                        .collect(Collectors.toSet());

        // toCollection()，想要返回什么容器，就 new 一个
                ArrayList<User> collection = userList.stream()
                        .filter(e -> e.getAge() < 10)
                        .filter(e -> e.getSex() == "男")
                        .collect(Collectors.toCollection(
                                () -> new ArrayList<>()
                        ));
    }

    /**
     * 这里插播一条新闻：如何将流转为数组？
     * Stream 提供了方法 toArray():
     *      Object[] toArray();
     *      <A> A[] toArray(IntFunction<A[]> generator);
     */
    @Test
    public void test8(){
        Object[] nameArray = userList.stream()
                .map(e -> e.getName())
                .toArray();
        Arrays.stream(nameArray)
                .forEach(System.out::println);

        // 转为 User 对象数组
        User[] users = userList.stream()
                .filter(e -> e.getSex() == "女")
                .toArray(User[]::new);
        Arrays.stream(users)
                .forEach(System.out::println);

        /*
        toStringBuilder
        不使用收集器的情况下
            谁能告诉我在Java中怎么单独使用三元运算符？s = s.length() > 0 ? s.append("-" + e) : s.append(e) 我想把 s = 省略掉，但 Java 中不行
         */
        StringBuilder joinName = userList.stream()
                .map(e -> e.getName())
                .collect(StringBuilder::new,
                        (s, e) ->  s = s.length() > 0 ? s.append("-" + e) : s.append(e),
                        (s1, s2) -> s1.append(s2)
                );

        System.out.println("toStringBuilder -> 使用 Collectors 类提供的收集器：");
        /*
        使用 Collectors 类提供的收集器：
        至于 Collectors.joining() 参数分别代表什么含义，看一下它们的参数名称，就明白了
            public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,    // 分隔符
                                                             CharSequence prefix,   // 前缀
                                                             CharSequence suffix)   // 后缀
         */
        String joinName1 = userList.stream()
                .map(e -> e.getName())
                .collect(Collectors.joining());

        String joinName2 = userList.stream()
                .map(e -> e.getName())
                .collect(Collectors.joining("-"));

        String joinName3 = userList.stream()
                .map(e -> e.getName())
                .collect(Collectors.joining("-", "[", "]"));

    }

    /**
     * toStringBuilder
     */
    @Test
    public void test9(){
/*
        toStringBuilder
        不使用收集器的情况下
            谁能告诉我在Java中怎么单独使用三元运算符？s = s.length() > 0 ? s.append("-" + e) : s.append(e) 我想把 s = 省略掉，但 Java 中不行
         */
        StringBuilder joinName = userList.stream()
                .map(e -> e.getName())
                .collect(StringBuilder::new,
                        (s, e) ->  s = s.length() > 0 ? s.append("-" + e) : s.append(e),
                        (s1, s2) -> s1.append(s2)
                );

        System.out.println("toStringBuilder -> 使用 Collectors 类提供的收集器：");
        /*
        使用 Collectors 类提供的收集器：
        至于 Collectors.joining() 参数分别代表什么含义，看一下它们的参数名称，就明白了
            public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,    // 分隔符
                                                             CharSequence prefix,   // 前缀
                                                             CharSequence suffix)   // 后缀
         */
        String joinName1 = userList.stream()
                .map(e -> e.getName())
                .collect(Collectors.joining());

        String joinName2 = userList.stream()
                .map(e -> e.getName())
                .collect(Collectors.joining("-"));

        String joinName3 = userList.stream()
                .map(e -> e.getName())
                .collect(Collectors.joining("-", "[", "]"));

        String joinName4 = userList.stream()
                .map(e -> e.getName())
                .collect(Collectors.joining(","));
    }

    /**
     * toMap
     *      在 Collectors 中一共有3个 toMap(),它们用来处理不同的问题
     */
    @Test
    public void test10(){
        /**
         * 两个参数的 toMap
         *      Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
         *                                     Function<? super T, ? extends U> valueMapper) {
         *         return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
         *     }
         *
         *   参数 keyMapper 用来获取key；valueMapper 用来获取 value
         *
         *  它的内部调用了四个参数的 toMap() 方法
         */

        Map<Integer, User> map1 = userList.stream()
                .collect(Collectors.toMap(User::getId, Function.identity()));
        System.out.println(map1);
        // Function.identity() 等价于 e -> e

        // select id, name, gender from user
                Map<Integer, Map<String, Object>> map2 = userList.stream()
                        .collect(Collectors.toMap(e -> e.getId(), e -> {
                            Map<String, Object> map = new HashMap<>();
                            map.put("gender", e.getSex());
                            map.put("name", e.getName());
                            map.put("id", e.getId());
                            return map;
                        }));
                System.out.println(map2);

                //你：如果 key 冲突了咋办？
                //Java8：你想咋办就咋办

        /**
         * 三个参数的 toMap
         *      Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
         *                                     Function<? super T, ? extends U> valueMapper,
         *                                     BinaryOperator<U> mergeFunction) {
         *         return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
         *     }
         *
         *     第三个参数 mergeFunction 就是用来处理 key 键冲突的
         *
         *      内部也是调用了四个参数的 toMap() 方法
         */

        // 如果 key 冲突，那么将冲突的 value 值拼接在一起
        Map<String, String> map3 = userList.parallelStream()
                .collect(Collectors.toMap(
                        e -> e.getSex(),
                        e -> e.getName(),
                        (o1, o2) -> o1 + ", " + o2
                        )
                );
        System.out.println(map3);
        //你：我想自己 new 一个 Map 对象

        /**
         * 四个参数的 toMap
         *      Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
         *                             Function<? super T, ? extends U> valueMapper,
         *                             BinaryOperator<U> mergeFunction,
         *                             Supplier<M> mapSupplier)
         *
         *                     参数 mapSupplier 用来提供返回容器
         */
        LinkedHashMap<String, String> map4 = userList.parallelStream()
                .collect(Collectors.toMap(e -> e.getSex(), e -> e.getName(), (o1, o2) -> o1 + ", " + o2, LinkedHashMap::new));
        System.out.println(map4);

    }

    /**
     * reducing
     * 单参数和两参数的 reducing()
     *      Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op)
     *      Collector<T, ?, U> reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op)
     *
     *      参数 identity 表示返回结果的初始值
     */
    @Test
    public void test11(){
        Optional<String> names1 = userList.stream()
                .map(User::getName)
                .collect(Collectors.reducing((e1, e2) -> e1 + "," + e2));
        System.out.println(names1.get());

        // 等同于
        String names2 = userList.stream()
                .collect(Collectors.reducing(
                        "", (e) -> e.getName(), (e1, e2) -> e1 + "," + e2)
                );
        System.out.println(names2);

        /**
         * 三参数的 reducing()
         *      reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op)
         *
         *      identity 是初始值，mapper 会对元素先进行一次处理，然后 op 对元素进行归约操作
         *
         * 注意： 返回类型要和参数 identity 的一致。
         * 你也许会纳闷，为什么有的返回一个 Optional<String> 类型数据，而有的就返回了 String
         * 因为含有参数 identity 的 reduing 方法中返回值有初始值，也就是 identity，所以不会出现空的情况
         *
         */
        //下面Collectors 提供的一些常用归约收集器
        // minBy、maxBy
        Optional<User> minAgeUser = userList.stream()
                .collect(Collectors.minBy((o1, o2) -> o1.getAge() - o2.getAge()));

        // counting
                Long count = userList.stream()
                        .collect(Collectors.counting());

        // summingInt、summingLong、summingDouble、
                Integer sumAge = userList.stream()
                        .collect(Collectors.summingInt(User::getAge));

        // averagingInt、averagingLong、averagingDouble
        // 平均值内部是总值/数量，所以返回值是浮点数 dobule
                Double avgAge = userList.stream()
                    .collect(Collectors.averagingInt(User::getAge));
    }

    /**
     * group by
     */
    @Test
    public void test12(){
        Map<String, List<User>> map = userList.stream()
                .collect(Collectors.groupingBy(User::getSex));

        /**
         * SQL 中的 group by 结果集中只能包含分组字段和聚合函数计算结果，这段代码比它更加全面
         *
         * 我们使用如下语句输出结果
         */
        map.keySet().stream()
                .forEach((e) -> {
                    System.out.println(e + "=" + map.get(e));
                });

        /**
         * 它真的分组了！！这是真正的分组
         *
         * 那怎么对分组中的元素进行操作呢，像 SQL 那样？？
         *
         * 完全不用担心，Collectors 提供了三个 groupBy 方法返回分组收集器
         *      Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T,? extends K> classifier)
         *
         *      Collector<T, ?, Map<K, D>> groupingBy(Function<? super T,? extends K> classifier,
         *                                       Collector<? super T,A,D> downstream)
         *
         *      Collector<T, ?, M> groupingBy(Function<? super T,? extends K> classifier,
         *                               Supplier<M> mapFactory,
         *                               Collector<? super T,A,D> downstream)
         *
         *               让我们放飞想象的翅膀，思考一下这几个参数分别有什么用。
         *
         *               downstream ？有 down 就表示有 up。那么谁是 upstream，很明显是 userList.stream，那么 downstream 就是分组集合的流喽。猜测 downstream 收集器是对分组中的元素进行归约操作的，就像是分组 SQL 语句字段中的聚合操作一样
         *
         */
        // select gender, count(*) from user group by gender
        Map<String, Long> map2 = userList.stream()
                .collect(Collectors.groupingBy(User::getSex, Collectors.counting()));
        System.out.println(map2);

    }

    /**
     * Stream 能实现的功能，for 循环都能实现，只是 Stream 代码一般比较简洁，可读性强。
     * 但在某些情况下，使用 for 循环要比 Stream 要简洁代码逻辑清晰
     */
    //在这个例子中，使用 for 循环要比 使用 stream 干净利落的多，代码逻辑清晰简明，可读性也比 stream 好。
    @Test
    public void test13(){
        /*private List<Order> orderList = Arrays.asList(
                new Order(103),
                new Order(106),
                new Order(107),
                new Order(104),
                new Order(102),
                new Order(103),
                new Order(102),
                new Order(101),
                new Order(104),
                new Order(102),
                new Order(105)
        );
        // 现根据 userId 设置 Order 对象的 name 属性

        // 使用 stream
        List<Order> newOrderList = orderList.stream()
                .map(o -> userList.stream()
                        .filter(u -> u.getId() == o.getUserId())
                        .findFirst()
                        .map(u -> {
                            o.setUserName(u.getName());
                            return o;
                        })
                        .orElse(o))
                .collect(Collectors.toList());
        newOrderList.stream().forEach(System.out::println);

        // 使用 for 循环
        for (Order o : orderList) {
            for (User u : userList) {
                if (o.getUserId() == u.getId()) {
                    o.setUserName(u.getName());
                    break;
                }
            }
        }
        orderList.stream().forEach(System.out::println);*/
    }

    @Test
    public void test14() throws IOException {
        //String format = DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN);
        //File file = new File("http://27.17.43.146:18002/group1/M00/00/00/wKgAHV7B6RWASU0jAABefyi2mJ4527.jpg");

        DateTime dateTime = DateUtil.lastMonth();
        int year = DateUtil.year(dateTime);
        int month = DateUtil.month(dateTime);

        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR, year);
        calendar.set(Calendar.MONTH, month);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        calendar.set(Calendar.DATE, calendar.getActualMaximum(Calendar.DATE));

        Date time = calendar.getTime();
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd ");
        String format1 = format.format(calendar.getTime());
    }

}
